package com.yubest.fs.service.impl;

import com.qiniu.common.QiniuException;
import com.qiniu.http.Response;
import com.qiniu.storage.BucketManager;
import com.qiniu.storage.Configuration;
import com.qiniu.storage.Region;
import com.qiniu.storage.UploadManager;
import com.qiniu.storage.model.FileInfo;
import com.qiniu.util.Auth;
import com.qiniu.util.StringMap;
import com.yubest.fs.bean.*;
import com.yubest.fs.config.FileStorageProperties;
import com.yubest.fs.consts.Consts;
import com.yubest.fs.exception.StorageException;
import lombok.Data;
import lombok.experimental.Accessors;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;

/**
 * @Author hweiyu
 * @Description
 * @Date 2022/2/9 18:09
 */
public class QiniuStorageImpl extends AbstractStorage<QiniuStorageImpl.QiniuClient> {

    private static final Logger log = LoggerFactory.getLogger(QiniuStorageImpl.class);

    private static final Map<String, Region> REGION_MAP = new HashMap<>();

    /**
     * metadata前缀
     */
    private static final String X_QN_META_PREFIX = "x-qn-meta-";

    static {
        //        华东	Region.region0(), Region.huadong()
        //        华北	Region.region1(), Region.huabei()
        //        华南	Region.region2(), Region.huanan()
        //        北美	Region.regionNa0(), Region.beimei()
        //        东南亚	Region.regionAs0(), Region.xinjiapo()
        REGION_MAP.put("huadong", Region.huadong());
        REGION_MAP.put("huabei", Region.huabei());
        REGION_MAP.put("huanan", Region.huanan());
        REGION_MAP.put("beimei", Region.beimei());
        REGION_MAP.put("xinjiapo", Region.xinjiapo());
    }

    @Override
    public void prepareClient(String bucket) {
        //校验配置信息是否完整
        if (null == getProps() || null == getProps().getQiniu()) {
            throw new StorageException("qiniu configuration is missing");
        }
        FileStorageProperties.Config config = this.getProps().getQiniu();
        List<FileStorageProperties.RegionBucket> buckets = config.getBuckets();
        if (CollectionUtils.isEmpty(buckets)) {
            throw new StorageException("qiniu buckets configuration is missing");
        }
        if (StringUtils.isEmpty(config.getAccessKey()) || StringUtils.isEmpty(config.getAccessSecret())) {
            throw new StorageException("qiniu accesKey, accessSecret configuration is missing");
        }
        //选择使用的桶，如果没传入，则使用默认的桶
        String useBucket = StringUtils.isEmpty(bucket) ? config.getPrimaryBucket() : bucket;
        FileStorageProperties.RegionBucket bucketInfo;
        //如果没指定桶，则使用第一个
        if (null == useBucket) {
            bucketInfo = buckets.get(0);
        } else {
            bucketInfo = buckets.stream()
                    .filter(e -> Objects.equals(useBucket, e.getBucket()))
                    .findFirst()
                    .orElseThrow(() -> new StorageException("qiniu bucket:[" + useBucket + "] is missing"));
        }
        if (null == bucketInfo.getRegion() || !REGION_MAP.containsKey(bucketInfo.getRegion().toLowerCase())) {
            throw new StorageException("qiniu, fetch region error");
        }
        Auth auth = Auth.create(config.getAccessKey(), config.getAccessSecret());
        //设置bucket所在的区域，@see com.yubest.fs.service.impl.QiniuStorageImpl.REGION_MAP
        Configuration cfg = new Configuration(REGION_MAP.get(bucketInfo.getRegion().toLowerCase()));
        //创建客户端
        QiniuClient qiniuClient = new QiniuClient()
                .setBucketInfo(bucketInfo)
                .setClient(new UploadManager(cfg))
                .setBucketManager(new BucketManager(auth, cfg))
                .setAuth(auth)
                .setToken(auth.uploadToken(bucketInfo.getBucket()));
        setStorageClient(qiniuClient);
    }

    @Override
    public PutResponse upload(PutFileRequest request) {
        PutResponse uploader = new PutResponse();

        FileStorageProperties.RegionBucket myBucket = getStorageClient().getBucketInfo();

        String path = getPath(myBucket.getPrefix(), request.getFileName());
        try {
            //封装metadata
            StringMap params = new StringMap();
            //设置用户自定义meta
            if (null != request.getMeta()) {
                for (Map.Entry<String, String> entry : request.getMeta().entrySet()) {
                    params.put(X_QN_META_PREFIX + entry.getKey(), entry.getValue());
                }
            }
            //设置内置属性
            params.put(X_QN_META_PREFIX + Consts.META_FILE_NAME,
                    !StringUtils.isEmpty(request.getFileName()) ? request.getFileName() : "");
            params.put(X_QN_META_PREFIX + Consts.META_FILE_CONTENT_TYPE,
                    !StringUtils.isEmpty(request.getContentType()) ? request.getContentType() : "");
            params.put(X_QN_META_PREFIX + Consts.META_FILE_SIZE, String.valueOf(request.getPayload().length));

            Response res = getStorageClient().put(request.getPayload(), path, params);
            if (!res.isOK()) {
                uploader.setCode(Consts.CODE_ERROR);
                uploader.setError(new StorageException("upload file error, " + res.toString()));
            } else {
                uploader.setPath(path);
                uploader.setUrl(this.getAccessUrl(new GetFileRequest().setBucket(request.getBucket()).setPath(path)));
            }
        } catch (Exception e){
            if (log.isErrorEnabled()) {
                log.error("upload file error, filePath:{}", path, e);
            }
            uploader.setCode(Consts.CODE_ERROR);
            uploader.setError(e);
        }
        return uploader;
    }

    @Override
    public FileStream download(GetFileRequest request) {
        try {
            String url = getAccessUrl(request);
            OkHttpClient okClient = new OkHttpClient();
            okhttp3.Response response = okClient.newCall(new Request.Builder().url(url).build()).execute();
            FileMeta meta = doGetMeta(request);
            return new FileStream(meta, Objects.requireNonNull(response.body()).bytes());
        } catch (Exception e) {
            if (log.isErrorEnabled()) {
                log.error("download file error, filePath:{}", request.getPath(), e);
            }
            throw new StorageException("download file error, file path:" + request.getPath());
        }
    }

    @Override
    public String getAccessUrl(GetFileRequest request) {
        FileStorageProperties.RegionBucket myBucket = getStorageClient().getBucketInfo();
        try {
            String rawUrl = myBucket.getDomain() + "/" + request.getPath();
            String url;
            //如果是私有的桶，封装时效性访问url
            if (Consts.PRIVATE_STORAGE.equals(myBucket.getType())) {
                url = getStorageClient().privateDownloadUrl(rawUrl, myBucket.getExpires());
            } else {
                url = rawUrl;
            }
            return url;
        } catch (Exception e) {
            if (log.isErrorEnabled()) {
                log.error("fetch file access path error, file path:{}", request.getPath(), e);
            }
            throw new StorageException("fetch file access path error, file path:" + request.getPath());
        }
    }

    @Override
    public FileMeta doGetMeta(GetFileRequest request) {
        FileStorageProperties.RegionBucket myBucket = getStorageClient().getBucketInfo();
        try {
            FileInfo fileInfo = getStorageClient().getBucketManager().stat(myBucket.getBucket(), request.getPath());
            FileMeta meta = new FileMeta();
            if (null != fileInfo && null != fileInfo.meta) {
                for (Map.Entry<String, Object> entry : fileInfo.meta.entrySet()) {
                    meta.add(entry.getKey(), String.valueOf(entry.getValue()));
                }
            }
            return meta;
        } catch (Exception e) {
            if (log.isErrorEnabled()) {
                log.error("fetch file meta error, file path:{}", request.getPath(), e);
            }
            throw new StorageException("fetch file meta, file path:" + request.getPath());
        }
    }

    @Override
    public boolean isFileExists(GetFileRequest request) {
        FileStorageProperties.RegionBucket myBucket = getStorageClient().getBucketInfo();
        try {
            getStorageClient().getBucketManager().stat(myBucket.getBucket(), request.getPath());
            return true;
        } catch (Exception e) {
            if (log.isErrorEnabled()) {
                log.error("does file exist error, file path:{}", request.getPath(), e);
            }
        }
        return false;
    }

    @Override
    public boolean delete(RemoveFileRequest request) {
        FileStorageProperties.RegionBucket myBucket = getStorageClient().getBucketInfo();
        try {
            getStorageClient().getBucketManager().delete(myBucket.getBucket(), request.getPath());
            return true;
        } catch (Exception e) {
            if (log.isErrorEnabled()) {
                log.error("delete file error, file path:{}", request.getPath(), e);
            }
            throw new StorageException("delete file error, file path:" + request.getPath());
        }
    }

    @Data
    @Accessors(chain = true)
    static class QiniuClient {

        private UploadManager client;

        private Auth auth;

        private String token;

        private BucketManager bucketManager;

        private FileStorageProperties.RegionBucket bucketInfo;

        Response put(byte[] data, String key, StringMap params) throws QiniuException {
            return getClient().put(data, key, getToken(), params, null, false);
        }

        String privateDownloadUrl(String baseUrl, long expires) {
            return getAuth().privateDownloadUrl(baseUrl, expires);
        }

    }
}

